#pragma once
#include <QString>
#include <QIcon>
#include <QByteArray>
#include <QUuid>
#include <QDateTime>
#include <QFile>
#include <QDebug>
#include <QFileInfo>

// 约定：在顶层目录的文件，直接使用全局命名空间；而在某个子目录下的文件，使用目录名称作为命名空间的名称
// 这样可以使代码逻辑更加清晰，尤其是在多层嵌套的目录下
namespace model
{

//----------------------------------------------------------------------------------------------------------------------------------
/// 工具函数
//----------------------------------------------------------------------------------------------------------------------------------

// 获取文件名称
static inline QString getFilename(const QString& path)
{
    QFileInfo fileInfo(path);
    return fileInfo.fileName();
}

// 封装日志宏函数 -- 对于日志宏的定义，往往是在项目开始的时候就需要确定的
// 宏是属于替换，在该命名空间外面展开的宏的话，就找不到getFilename函数，所以要加上命名空间
#define TAG QString("[%1:%2]").arg(model::getFilename(__FILE__), QString::number(__LINE__))
// #define TAG "[" << __LINE__ << "]"
// noquote()函数，是让qDebug打印的结果不带有双引号了
#define LOG() qDebug().noquote() << TAG

// 当函数定义在.h文件内，类外的时候，必须加上static或者inline，两个都加也可以，防止链接阶段出现函数重定义的问题
//获取时间戳
static inline int64_t getTime()
{
    // QDateTime::currentSecsSinceEpoch函数：得到一个秒级的时间戳
    return QDateTime::currentSecsSinceEpoch();
}
// 通过时间戳，转化为格式化时间
static inline QString formatTime(int64_t timestamp)
{
    // 先把时间戳转化为QDateTime对象
    // QDateTime::fromSecsSinceEpoch函数：将秒级时间戳转化为QDateTime对象
    QDateTime dateTime = QDateTime::fromSecsSinceEpoch(timestamp);
    // 将QDateTime对象转化为格式化时间
    return dateTime.toString("MM-dd:mm:ss");
}
// 根据QByteArray对象，转换为QIcon对象
// Qt的图片有三个类，QIcon、QPixmap以及QImage
// QIcon通常用来表示一个图片，QPixmap提供了很多关于图片的处理方法
static inline QIcon makeIcon(const QByteArray& byteArray)
{
    QPixmap pixmap;
    // 将二图片的二进制数据加载到QPixmap中
    pixmap.loadFromData(byteArray);
    // 根据QPixmap构造QIcon对象
    QIcon icon(pixmap);

    // 返回对象
    return icon;
}
// 读写文件操作
// 从指定文件中读取所有二进制内容，得到一个QByteArray对象
static inline QByteArray loadFileToByteArray(const QString& path)
{
    QFile file(path);
    bool ret_open = file.open(QFile::ReadOnly);
    //打开失败了
    if(ret_open == false)
    {
        LOG() << path << ":文件打开失败!";
        return QByteArray();
    }
    //读取文件
    QByteArray content = file.readAll();
    //关闭文件，不写close也可以，当对象的生命周期结束之后，会自动的关闭文件
    file.close();

    return content;
}
// 将QByteArrayy对象中的内容，写入到某个指定文件里
static inline void writeByteArrayToFile(const QString& path, const QByteArray& content)
{
    QFile file(path);
    bool ret_open = file.open(QFile::WriteOnly);
    if(ret_open == false)
    {
        LOG() << path << ":写入文件失败";
        return;
    }
    // 写入文件
    file.write(content);
    // 刷新文件缓冲区 -- 将缓冲区中的数据刷新到硬盘
    file.flush();
    // 关闭文件
    file.close();
    return;
}

//----------------------------------------------------------------------------------------------------------------------------------
/// 用户信息
//----------------------------------------------------------------------------------------------------------------------------------

class UserInfo
{
public:
    // 使用字符串可以更加灵活，如果是在单节点下，后端使用mysql数据库的整数自增主键的话，没有问题
    // 但是在分布式系统下，会对用户信息进行分库、分表，那么多个mysql各自自增的话，那么就会出现用户ID重复的情况了。
    // 也可以使用uuid，或者雪花算法等方式，生成一个唯一的用户ID
    QString userId = "";             // 用户编号
    QString nickName = "";           // 用户昵称
    QString description = "";        // 用户个性签名
    QString phoneNumber = "";        // 手机号码
    QIcon avatar;                    // 用户头像
};// end UserInfo

//----------------------------------------------------------------------------------------------------------------------------------
/// 消息信息
//----------------------------------------------------------------------------------------------------------------------------------

//消息类型--文本信息、图片信息、文件信息、语音信息
enum MessageType { TEXT_TYPE, FILE_TYPE, IMAGE_TYPE, SPEECH_TYPE };


class Message
{
public:
    QString messageId = "";                     // 消息ID
    QString chatsessionId = "";                 // 聊天会话ID：聊天会话与消息是一对多的关系，所以在消息结构中记录会话ID，可以方便找到是哪一个会话
    QString time = "";                          // 消息发送/接收的时间：使用格式化的结构表示时间，例如03-11 10：00：00
    MessageType messageType = TEXT_TYPE;        // 消息类型
    UserInfo sender;                            // 发送者信息
    QByteArray content;                         // 消息正文内容：消息如果是图片、文件和音频的话是二进制数据
    // 不适用string是因为，一个字符在C++中的存储所占空间是不一样的（英文字符一样，遵循ASCII编码，但是汉字不一样），
    // 在gbk编码下，一个汉字字符是2个字节，uft8下是三个字节
    // 如果我们想要在string中取出一段汉字中的一部分，那么就非常难，需要根据编码规则，来确定取出部分的起始位置在哪里。
    // 而Qt的QString对于字符和字节的区分就比较明确，对于不同编码下的字符集都有相应的处理规则。
    // 但是二进制流数据的话，必须使用QByteArray了，放到QString内部的话，会有不确定的风险
    // 例如：二进制数据存放到string之后，使用c系列函数处理二进制流数据，那么会因为0的存在而处理不完全。
    QString fileId = "";                        // 文件的身份标识：当消息类型为文件、图片和语音的时候才会有效，如果是文本消息的话就为空字符串
    // 对于文件/图片/音频小消息一般体积都会比较大，如果一个聊天会话中，包含多个上述这样的信息，就会使得从服务器中获取消息列表这样的操作，变得非常的低效
    // 一般的做法都是在获取消息列表的时候，只会获取文件/图片/语音消息的fileId，等到客户端收到之后，再去根据filed获取这些资源数据
    // 这样的话，就可以先把消息基本都先显示出来，对于文件/图片/语音这种大的数据，在慢慢的获取加载。
    QString filename = "";                      // 文件名称，当消息为文件的时候，才会有效，其他消息均为空字符串

    // 工厂函数--用于创建不同消息类型的对象
    // extraInfo只有在消息类型为文件的时候，作为文件名的补充字段
    static Message makeMessage(MessageType messageType, const QString& chatSessionId, const UserInfo& sender,
                               const QByteArray& content, const QString& extraInfo)
    {
        if(messageType == TEXT_TYPE)
        {
            return makeTextMessage(chatSessionId, sender, content);
        }
        else if(messageType == FILE_TYPE)
        {
            return makeFileMessage(chatSessionId, sender, content, extraInfo);
        }
        else if(messageType == IMAGE_TYPE)
        {
            return makeImageMessage(chatSessionId, sender, content);
        }
        else if(messageType == SPEECH_TYPE)
        {
            return makeSpeechMessage(chatSessionId, sender, content);
        }
        else
        {
            //出发了未知的消息类型
            return Message();
        }
    }
private:
    //生成唯一的messageID
    static QString makeMessageId()
    {
        // uuid是一套算法，通过该算法就能生成全球唯一的一个身份标识，他的结构是：xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 32位16进制数字
        // 实际开发中，为了提高可读性与存储空间的消耗，一般都是截取后面的12位16进制数字取出来
        // Qt内置了自动生成Uuid的函数
        // toString函数:转为QString类型
        // sliced函数：类似与substr，用于截取字符串
        // 为了方便确定是消息的ID，可以在字符串最前后加上一个标识符
        return "M" + QUuid::createUuid().toString().sliced(25, 12);
    }

private:
    //----------------------------------------------------------------------------------------------------------------------------------
    /// 构造各中类型的消息对象的函数
    //----------------------------------------------------------------------------------------------------------------------------------
    static Message makeTextMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content)
    {
        //构造一个message对象
        Message message;

        //进行message字段的填充
        //首先要确保，设置的messageId是唯一的
        message.messageId = makeMessageId();
        message.chatsessionId = chatSessionId;
        message.sender = sender;
        //生成一个格式化时间
        message.time = formatTime(getTime());
        message.messageType = TEXT_TYPE;
        message.content = content;
        //对于文本消息，下面的两个参数不使用
        message.fileId = "";
        message.filename = "";

        //返回对象
        return message;
    }
    static Message makeFileMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content, const QString& filename)
    {
        //构造一个message对象
        Message message;

        //进行message字段的填充
        //首先要确保，设置的messageId是唯一的
        message.messageId = makeMessageId();
        message.chatsessionId = chatSessionId;
        message.sender = sender;
        //生成格式化时间
        message.time = formatTime(getTime());
        message.messageType = FILE_TYPE;
        message.content = content;
        //对于文件身份标识字段，后续在进行设置
        message.fileId = "";
        //文件名字段
        message.filename = filename;

        //返回对象
        return message;

    }
    static Message makeImageMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content)
    {
        //构造一个message对象
        Message message;

        //进行message字段的填充
        //首先要确保，设置的messageId是唯一的
        message.messageId = makeMessageId();
        message.chatsessionId = chatSessionId;
        message.sender = sender;
        //生成格式化时间
        message.time = formatTime(getTime());
        message.messageType = FILE_TYPE;
        message.content = content;
        //对于文件身份标识字段，后续在进行设置
        message.fileId = "";
        //对于文件名字段不，不使用
        message.filename = "";

        //返回对象
        return message;
    }
    static Message makeSpeechMessage(const QString& chatSessionId, const UserInfo& sender, const QByteArray& content)
    {
        //构造一个message对象
        Message message;

        //进行message字段的填充
        //首先要确保，设置的messageId是唯一的
        message.messageId = makeMessageId();
        message.chatsessionId = chatSessionId;
        message.sender = sender;
        //生成格式化时间
        message.time = formatTime(getTime());
        message.messageType = FILE_TYPE;
        message.content = content;
        //对于文件身份标识字段，后续在进行设置
        message.fileId = "";
        //对于文件名字段不，不使用
        message.filename = "";

        //返回对象
        return message;
    }
};// end MessageType

//----------------------------------------------------------------------------------------------------------------------------------
/// 聊天会话信息
//----------------------------------------------------------------------------------------------------------------------------------

class ChatSessionInfo
{
public:
    QString chatSessionId = "";         // 会话编号
    QString chatSessionName = "";       // 会话名称：单聊的话就是对方的名称，群聊的话，就是群的名称
    Message newMessage;                 // 最新消息：用于在会话列表中起到显示-提示的效果
    QIcon avato;                        // 会话头像：如果会话是单聊就是对方的头像，群聊的话，就是群的头像
    QString userId = "";                // 用户ID：如果是单聊的话，就是对方的ID，如果是群聊的话，就先设置为空字符串，后续用其他方式把完整的用户id列表拿到
};// end CHatSessionInfo
} // end model
